﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Windows.Input;
using ViewMaker.Core;
using System.Linq.Expressions;

namespace ViewMaker
{
    [Obsolete("You should use a ViewModelBase class")]
    public class ViewModel : ViewModelBase
    {
    }

    /// <summary>
    /// ビューモデルのベースクラス
    /// </summary>
#if !SILVERLIGHT
    [Serializable]
#endif
    public class ViewModelBase : INotifyPropertyChanged, IViewCommand
    {

        #region INotifyPropertyChanged メンバー

        /// <summary>
        /// プロパティ変更通知イベント
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// プロパティ変更通知イベントを発生させる。
        /// </summary>
        /// <param name="names">プロパティ名</param>
        protected virtual void OnPropertyChanged(params string[] names)
        {
            if (PropertyChanged != null)
            {
                if (names == null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(null));
                }
                else
                {
                    foreach (string prop in names)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs(prop));
                    }
                }
            }
        }
        #endregion

        #region IViewCommand メンバー

        /// <summary>
        /// Viewコマンド通知イベント
        /// </summary>
#if !SILVERLIGHT
        [field: NonSerialized]
#endif
        public event EventHandler<ViewCommandEventArgs> ViewCommandNotified;

        /// <summary>
        /// コマンド実行前に動作する共通処理。
        /// </summary>
        /// <param name="command">コマンド名</param>
        /// <param name="parameter">コマンドパラメータ</param>
        protected internal virtual void OnCommandExecuting(ICommand command, object parameter)
        {
            if (ViewCommandNotified != null) ViewCommandNotified(this, new ViewCommandEventArgs { Command = ViewCommands.BeginCommand, Parameter = parameter });
        }

        /// <summary>
        /// コマンド実行後に動作する共通処理。
        /// </summary>
        /// <param name="command">コマンド名</param>
        /// <param name="error">エラー情報</param>
        protected internal virtual void OnCommandExecuted(ICommand command, Exception error)
        {
            if (ViewCommandNotified != null) ViewCommandNotified(this, new ViewCommandEventArgs { Command = ViewCommands.EndCommand, Parameter = error });
        }

        #endregion

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public ViewModelBase()
        {
        }

        /// <summary>
        /// サブのViewModelを作成します。
        /// ViewCommandNotifiedを集約を行うため、サブViewでのViewCommandがViewで処理されるようになります。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="args"></param>
        /// <returns></returns>
        public virtual T CreateSubViewModel<T>(params object[] args) where T : ViewModelBase
        {
            var subViewModel = (T)Activator.CreateInstance(typeof(T), args);
            subViewModel.ViewCommandNotified += new EventHandler<ViewCommandEventArgs>(subViewModel_ViewCommandNotified);
            return subViewModel;
        }

        void subViewModel_ViewCommandNotified(object sender, ViewCommandEventArgs e)
        {
            ExecuteViewCommand(e.Command, e.Parameter);
        }


        #region Command

        /// <summary>
        /// コマンドを作成する。
        /// </summary>
        /// <param name="action">コマンドで実行する処理</param>
        /// <param name="canExecute">コマンドが実行できるか判断する処理</param>
        /// <returns>作成されたコマンド</returns>
        protected RelayCommand CreateCommand(Action<object> action, Predicate<object> canExecute = null)
        {
            return new RelayCommand(this, action, canExecute);
        }

        /// <summary>
        /// コマンドを作成する。
        /// </summary>
        /// <param name="action">コマンドで実行する処理</param>
        /// <param name="canExecute">コマンドが実行できるか判断する処理</param>
        /// <returns>作成されたコマンド</returns>
        protected RelayCommand CreateCommand(Action action, Predicate<object> canExecute = null)
        {
            return new RelayCommand(this, action, canExecute);
        }

        /// <summary>
        /// コマンドを作成する。
        /// </summary>
        /// <param name="action">コマンドで実行する処理</param>
        /// <param name="canExecute">コマンドが実行できるか判断する処理</param>
        /// <returns>作成されたコマンド</returns>
        protected RelayCommand CreateCommand(Action action, Func<bool> canExecute)
        {
            var predicate = canExecute == null ? null : new Predicate<object>((x) => canExecute());
            return new RelayCommand(this, action, predicate);
        }


        /// <summary>
        /// Viewコマンド通知を行うコマンドを作成する
        /// </summary>
        /// <param name="command"></param>
        /// <param name="parameter"></param>
        /// <returns></returns>
        protected RelayCommand CreateViewCommand(string command, object parameter = null)
        {
            return CreateCommand(() => ExecuteViewCommand(command, parameter));
        }

        /// <summary>
        /// Viewコマンド通知を実行する。
        /// </summary>
        /// <param name="command">コマンド名</param>
        /// <param name="parameter">コマンドパラメータ</param>
        public void ExecuteViewCommand(string command, object parameter = null)
        {
            if (ViewCommandNotified != null) ViewCommandNotified(this, new ViewCommandEventArgs { Command = command, Parameter = parameter });
        }

        #endregion

        #region Validation

        /// <summary>
        /// 入力検証を実行する。
        /// </summary>
        /// <param name="areaName">Viewの領域名。nullの場合は全領域。View側で対応する項目を判断して入力検証を実行する</param>
        /// <returns>検証処理でエラーが発生した場合:false、エラーが発生しない場合:true</returns>
        public bool Validate(string areaName = null)
        {
            var info = new ValidateViewCommandInfo { AreaName = areaName };
            ExecuteViewCommand(ViewCommands.Validate, info);
            return info.IsValid;
        }

        /// <summary>
        /// 指定したプロパティの入力検証を実行してエラー情報を取得する。
        /// 検証ルールは該当プロパティの属性でする。
        /// </summary>
        /// <param name="propName">プロパティ名</param>
        /// <param name="value">検証する値</param>
        /// <returns>エラー情報。エラーが無い場合はnull</returns>
        protected ValidationException GetPropertyError(string propName, object value)
        {
            var prop = GetType().GetProperty(propName);
            var result = new List<ValidationResult>();
            var context = new ValidationContext(this, ValidationIServiceProvider, ValidationItems);
            context.MemberName = propName;
            if (Validator.TryValidateValue(value, context, result, prop.GetCustomAttributes(typeof(ValidationAttribute), true).Cast<ValidationAttribute>())) return null;
            return new ValidationException(result.First(), null, value);
        }

        /// <summary>
        /// ValidationContextのItems
        /// </summary>
        protected virtual IDictionary<object, object> ValidationItems { get; set; }

        /// <summary>
        /// ValidationContextのServiceProvider
        /// </summary>
        protected virtual IServiceProvider ValidationIServiceProvider { get; set; }

        /// <summary>
        /// 指定したプロパティの入力検証を実行する。エラーが発生した場合はValidationExceptionが発生する。
        /// 検証ルールは該当プロパティの属性でする。
        /// </summary>
        /// <param name="propName">プロパティ名</param>
        /// <param name="value">検証する値</param>
        protected void ValidateProperty(string propName, object value)
        {
            var err = GetPropertyError(propName, value);
            if (err != null) throw err;
        }

        #endregion

        protected void SetValue<T>(string propName, out T property, T value)
        {
            ValidateProperty(propName, value);
            property = value;
            OnPropertyChanged(propName);
        }
    }

    public static class ObjectExtension
    {
        public static string ToSymbol<TObj, TProp>(this TObj @this, Expression<Func<TObj, TProp>> expression)
        {
            return ((MemberExpression)expression.Body).Member.Name;
        }
    }
}